using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace MVPlan.Engine.Modeling
{
    /// <summary>
    /// Defines a single Buhlmann compartment.
    /// </summary>
    [Serializable]
    public class Compartment
    {
        #region Members

        private double kHe, kN2;                // Time constants - calculated from halftimes
        private double aHe, bHe, aN2, bN2;      // A and b co-efficients
        private double ppHe, ppN2;              // partial pressure

        internal const double LOG2 = 0.69315;   // ln(2)

        #endregion

        #region Properties

        /// <summary> 
        /// Gets/sets partial pressure of Helium of the compartment in msw (fsw)
        /// </summary>
        virtual public double PpHe
        {
            get
            {
                return ppHe;
            }

            set
            {
                ppHe = value;
            }

        }

        /// <summary>
        /// Gets/sets Partial pressure of Nitrogen for the compartment in msw (fsw)
        /// </summary>
        virtual public double PpN2
        {
            get
            {
                return ppN2;
            }

            set
            {
                ppN2 = value;
            }

        }

        /// <summary>
        /// Gets/sets time constant K for Helium.
        /// </summary>
        virtual public double KHe
        {
            get
            {
                return kHe;
            }

            set
            {
                kHe = value;
            }

        }

        /// <summary>
        /// Gets/sets time constant K for Nitrogen.
        /// </summary>
        virtual public double KN2
        {
            get
            {
                return kN2;
            }

            set
            {
                kN2 = value;
            }
        }

        /// <summary>
        /// Gets/sets Buhlmann A factor for Helium.
        /// </summary>
        virtual public double AHe
        {
            get
            {
                return aHe;
            }

            set
            {
                aHe = value;
            }
        }

        /// <summary>
        /// Gets/sets Buhlmann B factor for Helium.
        /// </summary>
        virtual public double BHe
        {
            get
            {
                return bHe;
            }

            set
            {
                bHe = value;
            }
        }

        /// <summary>
        /// Gets/sets Buhlmann A factor for Nitrogen.
        /// </summary>
        virtual public double AN2
        {
            get
            {
                return aN2;
            }

            set
            {
                aN2 = value;
            }
        }

        /// <summary>
        /// Gets/sets Buhlmann B factor for Nitrogen.
        /// </summary>
        virtual public double BN2
        {
            get
            {
                return bN2;
            }

            set
            {
                bN2 = value;
            }
        }

        #endregion

        #region Constructors

        /// <summary> 
        /// Constructor for Compartments. Initialises partial pressures to zero.
        /// </summary>
        public Compartment()
        {
            // Initialise compartment pressures to zero
            ppHe = 0;
            ppN2 = 0;
        }

        #endregion

        #region Methods
        
        /// <summary> 
        /// Sets compartment's time constants.
        /// </summary>
        /// <param name="hHe">Halftime, helium</param>
        /// <param name="hN2">Halftime, Nitrogen</param>
        /// <param name="aHe">a coefficient, Helium</param>
        /// <param name="bHe">b coefficient, Helium</param>
        /// <param name="aN2">a coefficient, Nitrogen</param>
        /// <param name="bN2">b coefficient, Nitrogen</param>
        public virtual void SetCompartmentTimeConstants(double hHe, double hN2, double aHe, double bHe, double aN2, double bN2)
        {
            kHe = LOG2 / hHe; // Time constants
            kN2 = LOG2 / hN2;
            this.aHe = aHe; // Co-efficients
            this.bHe = bHe;
            this.aN2 = aN2;
            this.bN2 = bN2;
        }


        /// <summary> 
        /// Sets partial pressures of He and N2 - in msw (fsw)
        /// </summary>
        /// <param name="ppHe">Partial pressure of Helium</param>
        /// <param name="ppN2">Partial pressure of Nitrogen</param>
        /// <throws>MVPlan.Model.ModelStateException if partial pressures are < 0 or time < 0 </throws>
        public virtual void SetPP(double ppHe, double ppN2)
        {
            if (ppHe < 0 || ppN2 < 0)
            {
                Trace.WriteLine("ERROR: SetPP() is throwing exception, ppHe=" + ppHe + " ppN2=" + ppN2);
                throw new ModelStateException("Error in argument: Compartment.SetPP()");
            }
            else
            {
                this.ppHe = ppHe;
                this.ppN2 = ppN2;
            }
        }

        /// <summary> 
        /// Constant depth calculations. Uses instananeous equation: P = Po + (Pi - Po)(1-e^-kt)
        /// </summary>
        /// <param name="ppHeInspired">Partiap pressure of inspired helium</param>
        /// <param name="ppN2Inspired">Partial pressure of inspired Nitrogen</param>
        /// <param name="segTime">Segment time in minutes</param>
        /// <throws>MVPlan.Model.ModelStateException if partial pressures are < 0 or time < 0 </throws>
        public virtual void ConstDepth(double ppHeInspired, double ppN2Inspired, double segTime)
        {
            double ppHeNew, ppN2New; // NOT NECESSARY, New compartment inert gas pp after asc/dec

            if (ppHeInspired < 0 || ppN2Inspired < 0 || segTime < 0)
            {
                Trace.WriteLine("ERROR: ConstDepth() is throwing exception, ppHe=" + ppHeInspired + " ppN2=" + ppN2Inspired);
                throw new ModelStateException("Error in argument: Compartment.ConstDepth()");
            }

            ppHeNew = ppHe + ((ppHeInspired - ppHe) * (1 - System.Math.Exp((-kHe) * segTime)));
            ppN2New = ppN2 + ((ppN2Inspired - ppN2) * (1 - System.Math.Exp((-kN2) * segTime)));
            ppHe = ppHeNew; // Update compartment
            ppN2 = ppN2New;
        }

        /// <summary> 
        /// Ascend or descend calculation
        /// UsesEquation: P=Pio+R(t -1/k)-[Pio-Po-(R/k)]e^-kt
        /// </summary>
        /// <param name="ppHeInspired">Partiap pressure of inspired helium</param>
        /// <param name="ppN2Inspired">Partial pressure of inspired Nitrogen</param>
        /// <param name="rateHe">Rate of chnage of ppHe</param>
        /// <param name="rateN2">Rate of chnage of ppHe</param>
        /// <param name="segTime">Segment time in minutes</param>
        /// <throws>MVPlan.Model.ModelStateException if partial pressures are < 0 or time < 0 </throws>
        public virtual void AscDec(double ppHeInspired, double ppN2Inspired, double rateHe, double rateN2, double segTime)
        {
            double ppHeNew, ppN2New; // New compartment inert gas pp after asc/dec

            if (ppHeInspired < 0 || ppN2Inspired < 0 || segTime < 0)
            {
                Trace.WriteLine("ERROR: AscDec() is throwing exception, ppHe=" + ppHeInspired + " ppN2=" + ppN2Inspired);
                throw new ModelStateException("Error in argument: Compartment.AscDec()");
            }
            else
            {
                ppHeNew = ppHeInspired + rateHe * (segTime - (1.0 / kHe)) - (ppHeInspired - ppHe - (rateHe / kHe)) * System.Math.Exp((-kHe) * segTime);
                ppN2New = ppN2Inspired + rateN2 * (segTime - (1.0 / kN2)) - (ppN2Inspired - ppN2 - (rateN2 / kN2)) * System.Math.Exp((-kN2) * segTime);
                ppHe = ppHeNew; // Update compartment
                ppN2 = ppN2New;
            }
        }

        /// <summary> 
        /// Gets M-Value for given ambient pressure uning the Buhlmann equation
        /// 
        /// Pm = Pa/b +a         where: Pm = M-Value pressure,
        /// Pa = ambinet pressure
        /// a,b co-efficients
        /// 
        /// Not used for decompression but for display of M-value limit line
        /// Note that this does not factor gradient factors.
        /// </summary>
        /// <param name="p">= Pressure, ambient, absolute in msw (fws)</param>
        /// <returns>Maximum tolerated pressure in mws (fws)</returns>
        public virtual double GetMValueAt(double p)
        {
            double aHeN2, bHeN2;
            double pHeN2;

            pHeN2 = ppHe + ppN2; // Sum partial pressures
            
            // Calculate adjusted a, b coefficients based on those of He and N2
            aHeN2 = ((aHe * ppHe) + (aN2 * ppN2)) / pHeN2;
            bHeN2 = ((bHe * ppHe) + (bN2 * ppN2)) / pHeN2;

            return (p / bHeN2 + aHeN2);
        }

        /// <summary> 
        /// Gets Tolerated Absolute Pressure for the compartment.
        /// </summary>
        /// <param name="gf">= gradient factor, 0.1 to 1.0, typical 0.2 - 0.95</param>
        /// <returns> Maximum tolerated pressure in mws (fws)</returns>
        public virtual double GetMaxAmb(double gf)
        {
            double aHeN2, bHeN2;
            double pHeN2;

            pHeN2 = ppHe + ppN2; // Sum partial pressures
            
            // Calculate adjusted a, b coefficients based on those of He and N2
            aHeN2 = ((aHe * ppHe) + (aN2 * ppN2)) / pHeN2;
            bHeN2 = ((bHe * ppHe) + (bN2 * ppN2)) / pHeN2;

            return (pHeN2 - aHeN2 * gf) / (gf / bHeN2 - gf + 1.0);
        }

        /// <summary>
        /// Gets M-Value for a compartment, given an ambient pressure.
        /// </summary>
        /// <param name="pAmb">Ambient pressure</param>
        /// <returns> M-Value</returns>
        public virtual double GetMV(double pAmb)
        {
            double aHeN2, bHeN2;
            double pHeN2;
            
            pHeN2 = ppHe + ppN2; // Sum partial pressures
            
            // Calculate adjusted a, b coefficients based on those of He and N2
            aHeN2 = ((aHe * ppHe) + (aN2 * ppN2)) / pHeN2;
            bHeN2 = ((bHe * ppHe) + (bN2 * ppN2)) / pHeN2;

            return pHeN2 / (pAmb / bHeN2 + aHeN2);
        }

        #endregion
    }
 }
